#include "util.h"
#include "log.h"
#include "inet_sockets.h"
#include <signal.h>

#define BUF_SIZE 100
#define MAX_THREAD_CNT 10

typedef enum
{
    LS,
    UPLOAD,
    DOWNLOAD,
    QUIT
} option_t;

/*wocao, static variable!!!*/
pthread_mutex_t mutex;
pthread_mutex_t snd_mutex;
int lfd;

static void
SIGINT_HANDLER()
{
    pthread_mutex_destroy(&mutex);
    pthread_mutex_destroy(&snd_mutex);
    close(lfd);
    printf("Caught SIGINT, aborted\n");
    exit(EXIT_SUCCESS);
}

char *executeCommand(const char *command)
{
    char buffer[128];
    char *result = NULL;
    FILE *pipe = popen(command, "r");
    if (pipe == NULL)
    {
        fprintf(stderr, "Failed to execute command\n");
        return NULL;
    }

    size_t totalSize = 0;
    size_t bufferSize = sizeof(buffer);
    size_t bytesRead;
    while ((bytesRead = fread(buffer, 1, bufferSize, pipe)) > 0)
    {
        result = realloc(result, totalSize + bytesRead + 1);
        if (result == NULL)
        {
            fprintf(stderr, "Memory allocation failed\n");
            pclose(pipe);
            return NULL;
        }
        memcpy(result + totalSize, buffer, bytesRead);
        totalSize += bytesRead;
    }
    result[totalSize] = '\0';

    pclose(pipe);
    return result;
}

option_t parse(char buf[], char path[BUF_SIZE])
{
    if (strncmp(buf, "ls", 2) == 0)
    {
        /* List */
        return LS;
    }
    else if (strncmp(buf, "upload", 6) == 0)
    {
        strcpy(path, buf + 7);
        return UPLOAD;
    }
    else if (strncmp(buf, "download", 6) == 0)
    {
        strcpy(path, buf + 9);
        return DOWNLOAD;
    }
    else if (strncmp(buf, "quit", 4) == 0)
    {
        strcpy(path, buf + 9);
        return QUIT;
    }
    return QUIT + 1;
}

static int handleRequest(int cfd)
{
    char buf[BUFSIZ];
    char resp[BUF_SIZE], path[BUF_SIZE];
    ssize_t numRead;

    while (1)
    {
        if ((numRead = recv(cfd, buf, BUFSIZ, 0)) > 0)
        {
            option_t option = parse(buf, path);
            switch (option)
            {
            case LS:
            {
                log_info("ls request");
                char *tmp = executeCommand(buf);
                send(cfd, tmp, BUFSIZ, 0);
                free(tmp);
                log_info("ls success");
                break;
            }

            case UPLOAD:
            {
                log_info("upload request");
                char cat_path[BUF_SIZE + 10];
                int cnt = 0, file_size = 1;

                strcpy(cat_path, "./upload/");
                get_rand_fname(path, BUF_SIZE);
                strncat(cat_path, path, BUF_SIZE - strlen(cat_path) - 1);
                FILE *file = fopen(cat_path, "wb");
                if (file == NULL)
                {
                    perror("fopen");
                    break;
                }
                memset(buf, 0, BUFSIZ);
                recv(cfd, buf, BUFSIZ, 0);
                file_size = atoi(buf);
                log_info("Receiving %d bytes...\n", file_size);
                memset(buf, 0, BUFSIZ);
                send(cfd, "Receiving", 10, 0);
                while (1)
                {
                    numRead = recv(cfd, buf, BUFSIZ, 0);
                    if (numRead <= 0)
                        break;
                    if (fwrite(buf, 1, numRead, file) != numRead)
                    {
                        perror("fwrite");
                        break;
                    }
                    cnt += numRead;
                    memset(buf, 0, BUFSIZ);
                    if (cnt >= file_size)
                    {
                        break;
                    }
                }
                log_info("Uploaded from a Client finished!!!\n");
                fclose(file);
                send(cfd, "Received", 9, 0);
                break;
            }
            case DOWNLOAD:
            {
                log_info("download request");
                FILE *file = fopen(path, "rb");
                int cnt = 0;
                if (file == NULL)
                {
                    perror("fopen");
                    break;
                }

                fseek(file, 0, SEEK_END);
                long fileSize = ftell(file);
                fseek(file, 0, SEEK_SET);

                memset(resp, 0, BUF_SIZE);
                sprintf(resp, "%ld", fileSize);
                send(cfd, resp, strlen(resp) + 1, 0);
                sleep(1);
                memset(buf, 0, BUFSIZ);
                while ((numRead = fread(buf, 1, BUFSIZ, file)) > 0)
                {
                    if (send(cfd, buf, numRead, 0) <= 0)
                    {
                        break;
                    }
                    memset(buf, 0, BUFSIZ);
                }
                fclose(file);
                recv(cfd, buf, BUFSIZ, 0);
                log_info("Client %s\n", buf);
                break;
            }
            case QUIT:
                log_info("A Client Quit Success");
                send(cfd, "CONNECTION FINISHED", 20, 0);
                close(cfd);
                _exit(EXIT_SUCCESS);

            default:
                log_info("UNKNOWN COMMAND FROM CLIENT");
                send(cfd, "UNKNOWN COMMAND", 16, 0);
                break;
            }
        }
    }

    if (numRead == -1)
    {
        ERR_EXIT("read():");
    }
}

int main()
{
    int ret, cfd;
    socklen_t len;
    ssize_t send_to;

    ret = pthread_mutex_init(&mutex, NULL);
    if (ret != 0)
    {
        perror("pthread_mutex_init:");
        exit(-1);
    }
    pthread_mutex_init(&snd_mutex, NULL);
    if (ret != 0)
    {
        perror("pthread_mutex_init:");
        exit(-1);
    }

    if (signal(SIGINT, SIGINT_HANDLER) == SIG_ERR)
    {
        perror("signal failed:");
        exit(EXIT_FAILURE);
    }

    lfd = inetListen("172.28.11.66", "8080", 10);
    // lfd = inetListen("127.0.0.1", "8080", 10);
    if (lfd == -1)
    {
        ERR_EXIT("inetListen():");
    }

    printf("Server running, send SIGINT to interrupt the server plz...\n");
    while (1)
    {
        cfd = accept(lfd, NULL, NULL); /* Wait for connection */
        if (cfd == -1)
        {
            ERR_EXIT("accept():");
        }

        /* Handle each client request in a new child process */

        switch (fork())
        {
        case -1:
            ERR_EXIT("fork():");

        case 0:         /* Child */
            close(lfd); /* Unneeded copy of listening socket */
            handleRequest(cfd);
            _exit(EXIT_SUCCESS);

        default:        /* Parent */
            close(cfd); /* Unneeded copy of connected socket */
            break;      /* Loop to accept next connection */
        }
    }

    return 0;
}
